Multi-threaded issue.
Ben Golding
bgg@object-craft.com.au
Wed, 21 Feb 2001 12:59:01 +1100
--Apple-Mail-1523120058-1
content-transfer-encoding: 7bit
content-type: text/plain;
charset=us-ascii
On Tuesday, February 20, 2001, at 09:24 PM, Timothy J. Wood wrote:
> The short answer is that you need to do this operation in the main
> thread. There are many ways you could go about that.
>
> - You could use Distributed Objects and vend a proxy between the two
> threads. This has been known to work, but it is rather heavy weight and
> you can get into weird situations if you start passing proxies between a
> lot of threads (potential deadlock, weird converstation queuing
> behaviors, etc).
>
> - You can use OmniFoundation and take advantage of the work we've
> already done. If you do this, then, you can simply do:
>
> [object mainThreadPerformSelector: @selector(mySelector)];
>
> If you are currently in the main thread, the selector will be invoked
> immediately, otherwise it will be queued to be invoked in the main
> thread at the next available opportunite. Note that
> -mainThreadPerformSelector: will return immediately -- it will not wait
> for the selector to be invoked! You would need to add an additional
> synchronization primitive (probably an NSLock) to do this.
>
> - You can use NSPort/NSPortMessage to build your own inter-thread
> messaging system (NSPort is useful since NSRunLoop will listen for
> messages on the port if asked to and you can us it to notify the main
> thread to look in a queue of stuff to do).
>
>
> If you are comfortable with OmniNetworking, it should be little work
> to incorporate OmniFoundation into your app and then you'll have a
> single line approach to this problem.
I wrote some code with the assistance of Chris Kane at Apple and Douglas
Johnston <doug@timeinc.net> which allows a process to hook an ONTCPSocket
into the NSRunLoop so that AppKit UI events and socket I/O are managed in
a single place. This is akin to using a select() loop in traditional unix
programming.
In this case, instead of using an ONTCPSocket in your code, you would use a
TCPCFSocket (which is a subclass of ONTCPSocket) and then use the
-readWithNotification method to put the socket into the run loop as an
input source. This is done using CFSocket and CFRunLoop from the Core
Foundation framework.
As data comes in on the socket, TCPCFSocket will post a
TCPCFSocketReadCompletionNotification notification which is invoked synchronously
by the main RunLoop so that it plays nicely with the AppKit. This saves having
to use separate tasks for socket I/O and the AppKit functions and communicate
between them.
I've used them heavily in my Maxter app (a Napster client) and they seem to
work really well. Caveat: the code to handle writes to the socket which would
notify when the socket's queue is ready for more data is untested.
Hope this is of some help.
Ben.
--Apple-Mail-1523120058-1
content-disposition: attachment;
filename=TCPCFSocket.h
content-type: application/octet-stream;
x-unix-mode=0644;
name=TCPCFSocket.h
content-transfer-encoding: 7bit
// $Id: TCPCFSocket.h,v 1.8 2000/12/08 03:51:46 bgg Exp $
// Maxter
//
// Created by bgg on Sat Nov 25 2000.
// Copyright (c) 2000 Object Craft P/L. All rights reserved.
#import <Foundation/Foundation.h>
#import <OmniNetworking/OmniNetworking.h>
#import <OmniBase/rcsid.h>
@interface TCPCFSocket : ONTCPSocket
{
CFRunLoopSourceRef _runLoopSource;
CFSocketRef _cfSocketRef;
CFSocketContext _socketContext;
}
- (void)dealloc;
// action methods
- (void)dataAvailableWithNotification;
- (void)acceptWithNotification;
- (void)readWithNotification;
- (void)removeFromRunLoop;
- (void)acceptConnection;
// internal
- (void)_invalidateSocket;
- (void)_addToRunLoopWithCallBackType:(CFSocketCallBackType)callBackType;
- (void)_processCallBackType:(CFSocketCallBackType)type withData:(NSData *)data;
@end
extern NSString *TCPCFSocketException;
extern NSString *TCPCFSocketArgumentException;
extern NSString *TCPCFSocketDataAvailableNotification;
extern NSString *TCPCFSocketAcceptedNotification;
extern NSString *TCPCFSocketReadCompletionNotification;
extern NSString *TCPCFSocketNotificationONTCPSocketItem;
extern NSString *TCPCFSocketNotificationDataItem;
--Apple-Mail-1523120058-1
content-disposition: attachment;
filename=TCPCFSocket.m
content-type: application/octet-stream;
x-unix-mode=0644;
name=TCPCFSocket.m
content-transfer-encoding: 7bit
// TCPCFSocket.m
// Maxter
//
// Created by bgg on Sat Nov 25 2000.
// Copyright (c) 2000 Object Craft P/L. All rights reserved.
#import "TCPCFSocket.h"
RCS_ID("$Id: TCPCFSocket.m,v 1.13 2000/12/08 05:47:40 bgg Exp $");
// Based on work from Douglas Johnston <doug@timeinc.net>. Understanding the
// CoreFoundation call backs was due to his efforts, bravo!
static void _tcpCFSocketCallback(CFSocketRef s, CFSocketCallBackType type,
CFDataRef address, const void *data,
void *info);
const void *_tcpCFSocketRetainContext(const void *);
void _tcpCFReleaseSocketContext(const void *);
@implementation TCPCFSocket
- (void)dealloc
{
if (_runLoopSource)
[self removeFromRunLoop];
[self _invalidateSocket];
[super dealloc];
}
// action methods
- (void)dataAvailableWithNotification;
{
[self _addToRunLoopWithCallBackType:kCFSocketReadCallBack];
}
- (void)acceptWithNotification
{
[self _addToRunLoopWithCallBackType:kCFSocketDataCallBack];
}
- (void)readWithNotification
{
[self _addToRunLoopWithCallBackType:kCFSocketDataCallBack];
}
// need to overload acceptConnection to close the socket fd held open
// by the CFSocket
- (void)acceptConnection
{
[super acceptConnection];
[self _invalidateSocket];
}
// XXX add more? -writeWithNotification, etc ...?
// remove our listener from the run loop.
- (void)removeFromRunLoop
{
if (_runLoopSource == NULL)
return;
CFRunLoopRemoveSource([[NSRunLoop currentRunLoop] getCFRunLoop],
_runLoopSource,
kCFRunLoopDefaultMode);
if (CFRunLoopSourceIsValid(_runLoopSource))
CFRunLoopSourceInvalidate(_runLoopSource);
CFRelease(_runLoopSource);
_runLoopSource = NULL;
}
// internal methods
// closes file descriptor in CFSocket
- (void)_invalidateSocket
{
if (_cfSocketRef != NULL) {
if (CFSocketIsValid(_cfSocketRef))
CFSocketInvalidate(_cfSocketRef);
CFRelease(_cfSocketRef);
_cfSocketRef = NULL;
}
}
// Add the CFSocket to the run loop this way:
- (void)_addToRunLoopWithCallBackType:(CFSocketCallBackType)callBackType
{
_socketContext.version = 1;
_socketContext.retain = &_tcpCFSocketRetainContext;
_socketContext.release = &_tcpCFReleaseSocketContext;
_socketContext.copyDescription = NULL;
_socketContext.info = self; // arg passed to call back
_cfSocketRef = CFSocketCreateWithNative(NULL, [self socketFD], callBackType,
&_tcpCFSocketCallback, &_socketContext);
if (_runLoopSource != NULL) {
[NSException raise:TCPCFSocketException
format:@"_addToRunLoop:run loop active"];
}
_runLoopSource = CFSocketCreateRunLoopSource(NULL, _cfSocketRef, 0);
CFRunLoopAddSource([[NSRunLoop currentRunLoop] getCFRunLoop],
_runLoopSource,
kCFRunLoopDefaultMode);
}
// generate appropriate notification to alert caller that event has been
// recieved.
- (void)_processCallBackType:(CFSocketCallBackType)type withData:(NSData *)data
{
NSDictionary *ui;
ONTCPSocket *newSocket;
NSNotificationCenter *defCenter = [NSNotificationCenter defaultCenter];
switch (type) {
case kCFSocketNoCallBack: // no call back (how are we here?!)
[NSException raise:TCPCFSocketArgumentException format:@"impossible call back"];
break;
case kCFSocketReadCallBack: // data available or ready to accept
[self removeFromRunLoop];
[defCenter postNotificationName:TCPCFSocketDataAvailableNotification
object:self];
break;
case kCFSocketAcceptCallBack: // acception connected, new fd in data
// XXX this is RONG!
[NSException raise:TCPCFSocketException format:@"XXX not implemented (yet)"];
// newSocket = [ONTCPSocket socketWithFD:*(const int *)[data bytes]];
newSocket = nil;
ui = [NSDictionary dictionaryWithObjectsAndKeys:
newSocket, TCPCFSocketNotificationONTCPSocketItem,
nil];
[defCenter postNotificationName:TCPCFSocketAcceptedNotification
object:self
userInfo:ui];
break;
case kCFSocketDataCallBack: // data read from socket
ui = [NSDictionary dictionaryWithObjectsAndKeys:
data, TCPCFSocketNotificationDataItem,
nil];
[defCenter postNotificationName:TCPCFSocketReadCompletionNotification
object:self
userInfo:ui];
break;
default:
[NSException raise:TCPCFSocketArgumentException format:@"unknown CFSocket call back type: %d", type];
break;
}
}
@end
// call-back function from CFRunLoop to handle socket activity. We convert
// the passed object into an NSData and then deal with those details by
// calling a method in the instance of TCPCFSocket which requested this
// callback; the instance's address is passed as "info".
//
// I suspect that data already is an "NSData *" but anyway we can't tell that
// from here. Oh well.
static void
_tcpCFSocketCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address,
const void *data, void *info)
{
NSData *dataObj;
char *buf;
if (data != NULL) {
if ((buf = NSZoneMalloc(NSDefaultMallocZone(), CFDataGetLength(data))) == 0)
[NSException raise:NSMallocException format:@"_tcpCFSocketCallback"];
CFDataGetBytes(data, CFRangeMake(0, CFDataGetLength(data)), buf);
dataObj = [NSData dataWithBytesNoCopy:buf length:CFDataGetLength(data)];
} else
dataObj = nil;
[(TCPCFSocket *)info _processCallBackType:type withData:dataObj];
}
const void *
_tcpCFSocketRetainContext(const void *info)
{
return info;
}
void
_tcpCFReleaseSocketContext(const void *info)
{
// (do nothing)
}
NSString *TCPCFSocketException = @"TCPCFSocketException";
NSString *TCPCFSocketArgumentException = @"TCPCFSocketArgumentException";
NSString *TCPCFSocketDataAvailableNotification = @"TCPCFSocketDataAvailableNotification";
NSString *TCPCFSocketAcceptedNotification = @"TCPCFSocketAcceptedNotification";
NSString *TCPCFSocketReadCompletionNotification = @"TCPCFSocketReadCompletionNotification";
NSString *TCPCFSocketNotificationONTCPSocketItem = @"TCPCFSocketNotificationONTCPSocketItem";
NSString *TCPCFSocketNotificationDataItem = @"TCPCFSocketNotificationDataItem";
--Apple-Mail-1523120058-1--